/*
 *
 * Paros and its related class files.
 *
 * Paros is an HTTP/HTTPS proxy for assessing web application security.
 * Copyright (C) 2003-2004 Chinotec Technologies Company
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Clarified Artistic License
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * Clarified Artistic License for more details.
 *
 * You should have received a copy of the Clarified Artistic License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
// ZAP: 2012/11/21 Heavily refactored extension to support non-HTTP messages.
// ZAP: 2013/04/15 Issue 632: Manual Request Editor dialogue (HTTP) configurations not saved correctly
// ZAP: 2014/01/28 Issue 207: Support keyboard shortcuts 
// ZAP: 2014/12/12 Issue 1449: Added help button
// ZAP: 2015/08/07 Issue 1768: Update to use a more recent default user agent
// ZAP: 2017/08/10 Issue 3798: java.awt.Toolkit initialised in daemon mode

package org.parosproxy.paros.extension.manualrequest.http.impl;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.HeadlessException;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;

import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.extension.manualrequest.ManualRequestEditorDialog;
import org.parosproxy.paros.extension.manualrequest.MessageSender;
import org.parosproxy.paros.model.Model;
import org.parosproxy.paros.network.HttpHeader;
import org.parosproxy.paros.network.HttpMalformedHeaderException;
import org.parosproxy.paros.network.HttpMessage;
import org.parosproxy.paros.network.HttpRequestHeader;
import org.zaproxy.zap.PersistentConnectionListener;
import org.zaproxy.zap.extension.help.ExtensionHelp;
import org.zaproxy.zap.extension.httppanel.HttpPanel;
import org.zaproxy.zap.extension.httppanel.HttpPanelRequest;
import org.zaproxy.zap.extension.httppanel.HttpPanelResponse;
import org.zaproxy.zap.extension.httppanel.Message;
import org.zaproxy.zap.view.ZapMenuItem;


public class ManualHttpRequestEditorDialog extends ManualRequestEditorDialog {

	private static final long serialVersionUID = -5830450800029295419L;
    private static final Logger logger = Logger.getLogger(ManualHttpRequestEditorDialog.class);

	private ZapMenuItem menuItem;
	
	private HttpPanelSender sender;

	private RequestResponsePanel requestResponsePanel;
	private HttpPanelRequest requestPanel;
	private HttpPanelResponse responsePanel;


	private JToolBar footerToolbar = null;
	// footer elements
	private JLabel labelTimeElapse = null;
	private JLabel labelContentLength = null;
	private JLabel labelTotalLength = null;
	private String helpKey = null;

	public ManualHttpRequestEditorDialog(boolean isSendEnabled, String configurationKey) throws HeadlessException {
		this(isSendEnabled, configurationKey, null);
	}

	public ManualHttpRequestEditorDialog(boolean isSendEnabled, String configurationKey, String helpKey) throws HeadlessException {
		super(isSendEnabled, configurationKey);
		this.helpKey = helpKey;
		sender = new HttpPanelSender(getRequestPanel(), getResponsePanel());
		
		initialize();
	}
	
	@Override
	protected void initialize() {
		super.initialize();

		// add footer status bar
		getWindowPanel().add(getFooterStatusBar(), BorderLayout.SOUTH);
		
		//setting footer status bar label and separator
		getFooterStatusBar().add(getLabelTimeLapse());
		getFooterStatusBar().addSeparator();
		getFooterStatusBar().add(getLabelContentLength());
		getFooterStatusBar().addSeparator();
		getFooterStatusBar().add(getLabelTotalLength());
	}
	
	@Override
	public void setVisible(boolean show) {
		super.setVisible(show);
		
		switchToTab(0);
	}

	@Override
	public Class<? extends Message> getMessageType() {
		return HttpMessage.class;
	}

	@Override
	public Message getMessage() {
		return getRequestPanel().getMessage();
	}
	
	@Override
	public void setMessage(Message aMessage) {
		if (aMessage == null) {
			return;
		}
		
		getRequestPanel().setMessage(aMessage);
		getResponsePanel().setMessage(aMessage);
		setFooterStatus(null);
		switchToTab(0);
	}

	@Override
	protected MessageSender getMessageSender() {
		return sender;
	}
	
	@Override
	protected HttpPanelRequest getRequestPanel() {
		if (requestPanel == null) {
			requestPanel = new HttpPanelRequest(true, configurationKey);
			requestPanel.setEnableViewSelect(true);
			requestPanel.loadConfig(Model.getSingleton().getOptionsParam().getConfig());
		}
		return requestPanel;
	}
	
	private HttpPanelResponse getResponsePanel() {
		if (responsePanel == null) {
			responsePanel = new HttpPanelResponse(false, configurationKey);
			responsePanel.setEnableViewSelect(true);
			responsePanel.loadConfig(Model.getSingleton().getOptionsParam().getConfig());
		}
		return responsePanel;
	}

	@Override
	protected Component getManualSendPanel() {
		if (requestResponsePanel == null) {
			requestResponsePanel = new RequestResponsePanel(configurationKey, getRequestPanel(), getResponsePanel());
			
			if (helpKey != null) {
				JButton helpButton = new JButton();
				helpButton.setIcon(ExtensionHelp.getHelpIcon());
				helpButton.setToolTipText(Constant.messages.getString("help.dialog.button.tooltip"));
				helpButton.addActionListener(new java.awt.event.ActionListener() { 
					@Override
					public void actionPerformed(java.awt.event.ActionEvent e) {
						ExtensionHelp.showHelp(helpKey);
					}
				});
				requestResponsePanel.addToolbarButton(helpButton);
			}
			
			requestResponsePanel.addEndButton(getBtnSend());
			requestResponsePanel.addSeparator();
	
			requestResponsePanel.loadConfig();
		}
		return requestResponsePanel;
	}

	@Override
	protected void btnSendAction() {		
		send(requestPanel.getMessage());
	}

	@Override
	protected void postSend() {
		super.postSend();
		
		switchToTab(1);
        setFooterStatus((HttpMessage) getResponsePanel().getMessage());
	}

	/**
	 * Return the footer status bar object
	 * @return
	 */
	protected JToolBar getFooterStatusBar() {
		if (footerToolbar == null) {
			footerToolbar = new JToolBar();
			footerToolbar.setEnabled(true);
			footerToolbar.setFloatable(false);
			footerToolbar.setRollover(true);
			footerToolbar.setName("Footer Toolbar Left");
			footerToolbar.setBorder(BorderFactory.createEtchedBorder());
		}
		return footerToolbar;
	}
	
	private void setFooterStatus(HttpMessage msg) {
		if (msg != null) {
			//get values
			long contentLength = msg.getResponseBody().length();
			long totalLength = msg.getResponseHeader().toString().length() + contentLength;
			long timeLapse =msg.getTimeElapsedMillis(); 
			// show time lapse and content length between request and response Constant.messages.getString("manReq.label.timeLapse")
			getLabelTimeLapse().setText(
					Constant.messages.getString("manReq.label.timeLapse") + String.valueOf(timeLapse) + " ms"); 
			getLabelContentLength().setText(
					Constant.messages.getString("manReq.label.contentLength") + String.valueOf(contentLength) + " " + Constant.messages.getString("manReq.label.totalLengthBytes"));
			getLabelTotalLength().setText(
					Constant.messages.getString("manReq.label.totalLength") + String.valueOf(totalLength) + " " + Constant.messages.getString("manReq.label.totalLengthBytes"));
		} else {
			getLabelTimeLapse().setText(Constant.messages.getString("manReq.label.timeLapse")); 
			getLabelContentLength().setText(Constant.messages.getString("manReq.label.contentLength"));
			getLabelTotalLength().setText(Constant.messages.getString("manReq.label.totalLength"));
		}
	}

	private void switchToTab(int i) {
		if (requestResponsePanel != null) {
			requestResponsePanel.switchToTab(i);
		}
	}

	@Override
	protected void saveConfig() {
		requestResponsePanel.saveConfig();
	}

	@Override
	public ZapMenuItem getMenuItem() {
		if (menuItem == null) {
			menuItem = new ZapMenuItem("menu.tools.manReq",
					KeyStroke.getKeyStroke(KeyEvent.VK_M, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false));
			menuItem.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					Message message = getMessage();
					if (message == null) {
					    setDefaultMessage();
					} else if (message instanceof HttpMessage && ((HttpMessage)message).getRequestHeader().isEmpty()) {
						setDefaultMessage();
				    }
					setVisible(true);
				}
			});
		}
		return menuItem;
	}

	@Override
	public void clear() {
		super.clear();
		
		getResponsePanel().clearView();
	}

	@Override
	public void setDefaultMessage() {
		HttpMessage msg = new HttpMessage();
		try {
			URI uri = new URI("http://www.any_domain_name.org/path", true);
			msg.setRequestHeader(
					new HttpRequestHeader(HttpRequestHeader.GET, uri, HttpHeader.HTTP10,
							Model.getSingleton().getOptionsParam().getConnectionParam()));
			setMessage(msg);
		} catch (HttpMalformedHeaderException e) {
			logger.error(e.getMessage(), e);
		} catch (URIException e) {
		    logger.error(e.getMessage(), e);
        }
	}
	
    /**
     * Get Label status time lapse
     * @return
     */
	private JLabel getLabelTimeLapse(){
		if (labelTimeElapse == null){
			labelTimeElapse = new JLabel("", JLabel.LEADING);
		}
		return labelTimeElapse;
	}
	
	/**
     * Get Label status Content Length
     * @return
     */
	private JLabel getLabelContentLength(){
		if (labelContentLength == null){
			labelContentLength = new JLabel("", JLabel.LEADING);
		}
		return labelContentLength;
	}
	
	/**
     * Get Label status Total Length
     * @return
     */
	private JLabel getLabelTotalLength(){
		if (labelTotalLength == null){
			labelTotalLength = new JLabel("", JLabel.LEADING);
		}
		return labelTotalLength;
	}
	
	private static final class RequestResponsePanel extends JPanel {
		
		private static final String REQUEST_CAPTION = Constant.messages.getString("manReq.tab.request");
		private static final String RESPONSE_CAPTION = Constant.messages.getString("manReq.tab.response");

		private static final String TABS_VIEW_TOOL_TIP = Constant.messages.getString("manReq.display.tabs");
		private static final String ABOVE_VIEW_TOOL_TIP = Constant.messages.getString("manReq.display.above");
		private static final String SIDE_BY_SIDE_VIEW_TOOL_TIP = Constant.messages.getString("manReq.display.sidebyside");
		
		private static final String SELECTEDLAYOUT_CONFIG_KEY = "selectedlayout";
		private static final String HORIZONTAL_DIVIDER_LOCATION_CONFIG_KEY = "horizontalDividerLocation";
		private static final String VERTICAL_DIVIDER_LOCATION_CONFIG_KEY = "verticalDividerLocation";

		private static final long serialVersionUID = -3335708932021769432L;
		
		private static final int TABS_VIEW = 0;
		private static final int ABOVE_VIEW = 1;
		private static final int SIDE_BY_SIDE_VIEW = 2;
		
		private final HttpPanelRequest requestPanel;
		private final HttpPanelResponse responsePanel;
		
		private int currentView;
		private JComponent currentViewPanel;
		private JToggleButton currentButtonView;
		
		private JToggleButton tabsButtonView;
		private JToggleButton aboveButtonView;
		private JToggleButton sideBySideButtonView;
		
		private String configurationKey;
		
		private int verticalDividerLocation;
		private int horizontalDividerLocation;
	
		public RequestResponsePanel(String configurationKey, HttpPanelRequest request, HttpPanelResponse response) throws IllegalArgumentException {
			super(new BorderLayout());
			if (request == null || response == null) {
				throw new IllegalArgumentException("The request and response panels cannot be null.");
			}
			
			
			this.configurationKey = configurationKey;
	
			this.requestPanel = request;
			this.responsePanel = response;
			
			this.currentView = -1;
			
			tabsButtonView = new JToggleButton(new ImageIcon(ManualRequestEditorDialog.class.getResource("/resource/icon/layout_tabbed.png")));
			tabsButtonView.setToolTipText(TABS_VIEW_TOOL_TIP);
			
			tabsButtonView.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					changeView(TABS_VIEW);
				}
			});
			
			addToolbarButton(tabsButtonView);
			
			aboveButtonView = new JToggleButton(new ImageIcon(ManualRequestEditorDialog.class.getResource("/resource/icon/layout_vertical_split.png")));
			aboveButtonView.setToolTipText(ABOVE_VIEW_TOOL_TIP);
			
			aboveButtonView.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					changeView(ABOVE_VIEW);
				}
			});
			
			addToolbarButton(aboveButtonView);
			
			sideBySideButtonView = new JToggleButton(new ImageIcon(ManualRequestEditorDialog.class.getResource("/resource/icon/layout_horizontal_split.png")));
			sideBySideButtonView.setToolTipText(SIDE_BY_SIDE_VIEW_TOOL_TIP);
			
			sideBySideButtonView.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					changeView(SIDE_BY_SIDE_VIEW);
				}
			});
			
			addToolbarButton(sideBySideButtonView);
		}
		
		public void loadConfig() {
			verticalDividerLocation = Model.getSingleton().getOptionsParam().getConfig().getInt(configurationKey + VERTICAL_DIVIDER_LOCATION_CONFIG_KEY, -1);
			horizontalDividerLocation = Model.getSingleton().getOptionsParam().getConfig().getInt(configurationKey + HORIZONTAL_DIVIDER_LOCATION_CONFIG_KEY, -1);
			
			changeView(Model.getSingleton().getOptionsParam().getConfig().getInt(configurationKey + SELECTEDLAYOUT_CONFIG_KEY, TABS_VIEW));
			
			requestPanel.loadConfig(Model.getSingleton().getOptionsParam().getConfig());
			responsePanel.loadConfig(Model.getSingleton().getOptionsParam().getConfig());
		}
		
		public void saveConfig() {
			switch(currentView) {
			case ABOVE_VIEW:
				verticalDividerLocation = ((JSplitPane)currentViewPanel).getDividerLocation();
				break;
			case SIDE_BY_SIDE_VIEW:
				horizontalDividerLocation = ((JSplitPane)currentViewPanel).getDividerLocation();
				break;
			default:
			}
			
			Model.getSingleton().getOptionsParam().getConfig().setProperty(configurationKey + VERTICAL_DIVIDER_LOCATION_CONFIG_KEY, Integer.valueOf(verticalDividerLocation));
			Model.getSingleton().getOptionsParam().getConfig().setProperty(configurationKey + HORIZONTAL_DIVIDER_LOCATION_CONFIG_KEY, Integer.valueOf(horizontalDividerLocation));
			
			Model.getSingleton().getOptionsParam().getConfig().setProperty(configurationKey + SELECTEDLAYOUT_CONFIG_KEY, Integer.valueOf(currentView));
			
			requestPanel.saveConfig(Model.getSingleton().getOptionsParam().getConfig());
			responsePanel.saveConfig(Model.getSingleton().getOptionsParam().getConfig());
		}
	
		public void addToolbarButton(JToggleButton button) {
			requestPanel.addOptions(button, HttpPanel.OptionsLocation.AFTER_COMPONENTS);
		}
		
		public void addToolbarButton(JButton button) {
			requestPanel.addOptions(button, HttpPanel.OptionsLocation.AFTER_COMPONENTS);
		}
		
		public void addSeparator() {
			requestPanel.addOptionsSeparator();
		}
		
		public void addEndButton(JButton button) {
			requestPanel.addOptions(button, HttpPanel.OptionsLocation.END);
		}
	
		public void switchToTab(int i) {
			if (currentView == TABS_VIEW) {
				((JTabbedPane) currentViewPanel).setSelectedIndex(i);
			}
		}
	
		public void changeView(int newView) {
			if (newView != currentView) {
				final int oldView = currentView;
				currentView = newView;
				
				if (oldView != -1) {
					this.removeAll();
					currentButtonView.setSelected(false);
					
					switch(oldView) {
					case ABOVE_VIEW:
						verticalDividerLocation = ((JSplitPane)currentViewPanel).getDividerLocation();
						break;
					case SIDE_BY_SIDE_VIEW:
						horizontalDividerLocation = ((JSplitPane)currentViewPanel).getDividerLocation();
						break;
					default:
					}
				}
				
				switch (newView) {
				case TABS_VIEW:
					switchToTabsView();
					break;
				case ABOVE_VIEW:
					switchToAboveView();
					break;
				case SIDE_BY_SIDE_VIEW:
					switchToSideBySideView();
					break;
				default:
					switchToTabsView();
					break;
				}
				
				currentButtonView.setSelected(true);
				
				this.add(currentViewPanel);
				
				this.validate();
				this.repaint();
			}
		}
		
		private void switchToTabsView() {
			currentView = TABS_VIEW;
			currentButtonView = tabsButtonView;
			
			final JTabbedPane tabbedPane = new JTabbedPane();
			tabbedPane.addTab(REQUEST_CAPTION, null, requestPanel, null);
			tabbedPane.addTab(RESPONSE_CAPTION, null, responsePanel, null);
			tabbedPane.setSelectedIndex(0);
			
			currentViewPanel = tabbedPane;
		}
		
		private void switchToAboveView() {
			currentView = ABOVE_VIEW;
			currentButtonView = aboveButtonView;
			
			currentViewPanel = createSplitPane(JSplitPane.VERTICAL_SPLIT);
		}
		
		private void switchToSideBySideView() {
			currentView = SIDE_BY_SIDE_VIEW;
			currentButtonView = sideBySideButtonView;
			
			currentViewPanel = createSplitPane(JSplitPane.HORIZONTAL_SPLIT);
		}
		
		private JSplitPane createSplitPane(int orientation) {
			final JTabbedPane tabbedPaneRequest = new JTabbedPane();
			tabbedPaneRequest.addTab(REQUEST_CAPTION, null, requestPanel, null);
	
			final JTabbedPane tabbedPaneResponse = new JTabbedPane();
			tabbedPaneResponse.addTab(RESPONSE_CAPTION, null, responsePanel, null);
			
			final JSplitPane splitPane = new JSplitPane(orientation, tabbedPaneRequest, tabbedPaneResponse);
			splitPane.setDividerSize(3);
			splitPane.setResizeWeight(0.5d);
			splitPane.setContinuousLayout(false);
			splitPane.setDoubleBuffered(true);
			
			int dividerLocation;
			if (orientation == JSplitPane.HORIZONTAL_SPLIT) {
				dividerLocation = horizontalDividerLocation;
			} else {
				dividerLocation = verticalDividerLocation;
			}
			splitPane.setDividerLocation(dividerLocation);
			
			return splitPane;
		}
	}
	
	public void addPersistentConnectionListener(PersistentConnectionListener listener) {
		((HttpPanelSender) getMessageSender()).addPersistentConnectionListener(listener);
	}

	public void removePersistentConnectionListener(PersistentConnectionListener listener) {
		((HttpPanelSender) getMessageSender()).removePersistentConnectionListener(listener);
	}
}
